﻿#include "io.hpp"

extern std::map<uint64_t, Atomic<std::array<uint8_t, 64>>> global_ram;

void SerialInput::in()
{
	if ((end != -1) && ((beg - end) == 1)) // buffer is full
	{
		setStatus(BAD);
		return;
	}
	beg = (beg + 1) & 0x3f;
	buffer[beg] = getchar();
	setStatus(DONE);
}
uint8_t SerialInput::out()
{
	if (beg == end) // buffer is empty
	{
		setStatus(BAD);
		return 0;
	}
	end = (end + 1) & 0x3f;
	setStatus(DONE);
	return buffer[end];
}

void SerialOutput::in(uint8_t data)
{
	if ((end != -1) && ((beg - end) == 1)) // buffer is full
	{
		setStatus(BAD);
		return;
	}
	else if ((beg == 0x3f) && (end == -1))
	{
		setStatus(BAD);
		return;
	}
	beg = (beg + 1) & 0x3f;
	buffer[beg] = data;
	setStatus(DONE);
}
void SerialOutput::out()
{
	if (beg == end) // buffer is empty
	{
		setStatus(BAD);
		return;
	}
	while (beg != end)
	{
		end = (end + 1) & 0x3f;
		putchar(buffer[end]);
	}
	setStatus(DONE);
}

void FileInput::setAddr(uint64_t addr)
{
	switch (getStatus())
	{
	case SET0:maddr = addr; setStatus(DONE); break;
	case SET1:faddr = addr; setStatus(DONE); break;
	case SET2:block = (addr & 0x7fff'ffff); setStatus(DONE); break;
	default:setStatus(BAD); break;
	}
}
const uint64_t FileInput::getAddr()const
{
	switch (getStatus())
	{
	case GET0: return maddr;
	case GET1: return faddr;
	case GET2: return block;
	default: return 0;
	}
}
void FileInput::read()
{
	if (!file.is_open())
	{
		setStatus(BAD);
		return;
	}
	if ((maddr & 0x3f) != 0)
	{
		setStatus(BAD);
		return;
	}
	file.seekg(faddr, std::ios::beg);
	for (int i = 0; i < block; i++)
	{
		std::array<uint8_t, 64> temp = {};
		file.read((char *)temp.data(), 64);
		read_buffer.push(temp);
	}
	std::thread t = std::thread(([this]() {
		uint64_t blockID = maddr >> 6;
		while (!read_buffer.empty())
		{
			if (global_ram.count(blockID) == 0)
				global_ram[blockID] = Atomic<std::array<uint8_t, 64>>();
			global_ram[blockID].w_lock();
			global_ram[blockID].get() = read_buffer.front();
			global_ram[blockID].w_unlock();
			read_buffer.pop();
			blockID = blockID++;
		}
		}));
	t.detach();
	setStatus(DONE);
}

void FileOutput::setAddr(uint64_t addr)
{
	switch (getStatus())
	{
	case SET0:maddr = addr; setStatus(DONE); break;
	case SET1:faddr = addr; setStatus(DONE); break;
	case SET2:block = (addr & 0x7fff'ffff); setStatus(DONE); break;
	default:setStatus(BAD); break;
	}
}
const uint64_t FileOutput::getAddr()const
{
	switch (getStatus())
	{
	case GET0: return maddr;
	case GET1: return faddr;
	case GET2: return block;
	default: return 0;
	}
}
void FileOutput::write()
{
	if (!file.is_open())
	{
		setStatus(BAD);
		return;
	}
	if ((maddr & 0x3f) != 0)
	{
		setStatus(BAD);
		return;
	}
	file.seekp(faddr, std::ios::beg);
	uint64_t blockID = maddr >> 6;
	for (int i = 0; i < block; i++)
	{
		if (global_ram.count(blockID) == 0)
		{
			setStatus(BAD);
			return;
		}
		global_ram[blockID].r_lock();
		write_buffer.push(global_ram[blockID].get());
		global_ram[blockID].r_unlock();
		blockID = blockID++;
	}
	std::thread t = std::thread(([this]() {
		while (!write_buffer.empty())
		{
			file.write((char *)write_buffer.front().data(), 64);
			write_buffer.pop();
		}
		}));
	t.detach();
	setStatus(DONE);
}

void FileIO::setAddr(uint64_t addr)
{
	switch (getStatus())
	{
	case SET0:maddr = addr; setStatus(DONE); break;
	case SET1:faddr = addr; setStatus(DONE); break;
	case SET2:block = (addr & 0x7fff'ffff); setStatus(DONE); break;
	default:setStatus(BAD); break;
	}
}
const uint64_t FileIO::getAddr()const
{
	switch (getStatus())
	{
	case GET0: return maddr;
	case GET1: return faddr;
	case GET2: return block;
	default: return 0;
	}
}
void FileIO::read()
{
	if (!file.is_open())
	{
		setStatus(BAD);
		return;
	}
	if ((maddr & 0x3f) != 0)
	{
		setStatus(BAD);
		return;
	}
	file.seekg(faddr, std::ios::beg);
	for (int i = 0; i < block; i++)
	{
		std::array<uint8_t, 64> temp = {};
		file.read((char *)temp.data(), 64);
		buffer.push(temp);
	}
	std::thread t = std::thread(([this]() {
		uint64_t blockID = maddr >> 6;
		while (!buffer.empty())
		{
			if (global_ram.count(blockID) == 0)
				global_ram[blockID] = Atomic<std::array<uint8_t, 64>>();
			global_ram[blockID].w_lock();
			global_ram[blockID].get() = buffer.front();
			global_ram[blockID].w_unlock();
			buffer.pop();
			blockID = blockID++;
		}
		}));
	t.detach();
	setStatus(DONE);
}
void FileIO::write()
{
	if (!file.is_open())
	{
		setStatus(BAD);
		return;
	}
	if ((maddr & 0x3f) != 0)
	{
		setStatus(BAD);
		return;
	}
	file.seekp(faddr, std::ios::beg);
	uint64_t blockID = maddr >> 6;
	for (int i = 0; i < block; i++)
	{
		global_ram[blockID].r_lock();
		buffer.push(global_ram[blockID].get());
		global_ram[blockID].r_unlock();
		blockID = blockID++;
	}
	std::thread t = std::thread(([this]() {
		while (!buffer.empty())
		{
			file.write((char *)buffer.front().data(), 64);
			buffer.pop();
		}
		}));
	t.detach();
	setStatus(DONE);
}